import { commands } from './commands'
// @ts-ignore
import { TextEncoder } from 'text-decoding'

const FONT_SIZE_MAP: { [key: string]: string } = {
  '1*1': 'SIZE_NORMAL',
  '1*2': 'SIZE_NORMAL_DOUBLE',
  '1*3': 'SIZE_NORMAL_TRIPLE',
  '1*4': 'SIZE_NORMAL_QUADRUPLE',
  '2*1': 'SIZE_DOUBLE_NORMAL',
  '2*2': 'SIZE_DOUBLE',
  '2*3': 'SIZE_DOUBLE_TRIPLE',
  '2*4': 'SIZE_DOUBLE_QUADRUPLE',
  '3*1': 'SIZE_TRIPLE_NORMAL',
  '3*2': 'SIZE_TRIPLE_DOUBLE',
  '3*3': 'SIZE_TRIPLE',
  '3*4': 'SIZE_TRIPLE_QUADRUPLE',
  '4*1': 'SIZE_QUADRUPLE_NORMAL',
  '4*2': 'SIZE_QUADRUPLE_DOUBLE',
  '4*3': 'SIZE_QUADRUPLE_TRIPLE',
  '4*4': 'SIZE_QUADRUPLE',
}

const BARCODE_TXT_MAP: { [key: number]: string } = {
  0: 'BARCODE_TXT_OFF',
  1: 'BARCODE_TXT_ABV',
  2: 'BARCODE_TXT_BLW',
  3: 'BARCODE_TXT_BTH',
}

function transformMmToLH(millimeter: number): number[] {
  const mm = Number(millimeter) || 0
  const n = Math.round(mm * 8)
  return getLHArr(n)
}

function getLHArr(n: number): number[] {
  const number = Number(n) || 0
  const nL = number % 256
  const nH = (number - nL) / 256
  return [nL, nH]
}

interface QRCodeParams {
  text: string
  mode?: number
  size?: number
  level?: number
}

interface BarcodeParams {
  text: string
  width?: 2 | 3 | 4 | 5 | 6
  height?: number
  textPosition?: number
  type?: string
}

class PrinterJobs {
  private _queue: number[]
  private _encoder: TextEncoder

  constructor() {
    this._queue = []
    this._encoder = new TextEncoder('gb2312', {
      NONSTANDARD_allowLegacyEncoding: true,
    })
    this._init()
  }

  private _enqueue(cmd: number[]): void {
    this._queue.push(...cmd)
  }

  private _init(): void {
    this._enqueue(commands.UNIT_INIT)
  }

  public setLine() {
    this._enqueue(commands.LF)
    console.log(this._queue)
  }

  /** 图像像素点转换为位图 */
  public convertPartialToBitmap(res: UniApp.CanvasGetImageDataRes) {
    const w = res.width
    const h = res.height
    const bitw = parseInt(String((w + 7) / 8)) * 8
    const bith = h
    const pitch = parseInt(String(bitw / 8))
    const bits = new Uint8Array(bith * pitch)

    this._enqueue([29]) // 0x1D
    this._enqueue([118]) // 0x76
    this._enqueue([48]) // 0x30
    this._enqueue([0]) // 0x00
    this._enqueue([parseInt(String(pitch % 256))])
    this._enqueue([parseInt(String(pitch / 256))])
    this._enqueue([parseInt(String(bith % 256))])
    this._enqueue([parseInt(String(bith / 256))])

    for (let y = 0; y < h; y++) {
      for (let x = 0; x < w; x++) {
        const color = res.data[(y * w + x) * 4]
        if (color < 128) {
          bits[parseInt(String(y * pitch + x / 8))] |= 0x80 >> x % 8
        }
      }
    }

    for (let i = 0; i < bits.length; i++) {
      this._enqueue([bits[i]])
    }
    console.log('_queue', this._queue)
  }

  public getLength(): number {
    return this._queue.length
  }

  public repeat(start = 0, end = 0, times = 0): PrinterJobs {
    const interval = this._queue.slice(start, end)
    for (let i = 0; i < times; i++) {
      this._enqueue(interval)
    }
    return this
  }

  public add(commands: number[]): PrinterJobs {
    this._enqueue(commands)
    return this
  }

  public text(content: string): PrinterJobs {
    if (content) {
      const uint8Array = this._encoder.encode(content)
      const encoded = Array.from(uint8Array)
      this._enqueue(encoded as number[])
    }
    return this
  }

  public printToEnd(): PrinterJobs {
    this._enqueue(commands.GS_FF)
    return this
  }

  public setAlign(align: 'TXT_ALIGN_LT' | 'TXT_ALIGN_CT' | 'TXT_ALIGN_RT'): PrinterJobs {
    this._enqueue(commands.TEXT_FORMAT[align])
    return this
  }

  public setFont(family: 'TXT_FONT_A' | 'TXT_FONT_B' | 'TXT_FONT_C'): PrinterJobs {
    this._enqueue(commands.TEXT_FORMAT[family])
    return this
  }

  public setPageWidth(width: number) {
    this._enqueue(commands.PAGE_WIDTH.SET_WIDTH(width))
    return this
  }

  public setImage(content: any): PrinterJobs {
    const cmds = [].concat(
      [27, 97, 1],
      [29, 118, 48, 0, 30, 0, 240, 0],
      content,
      [27, 74, 3],
      [27, 64],
    )
    this._enqueue(cmds)
    this._enqueue(commands.LF)
    return this
  }

  public setFontSize(width = 1, height = 1): PrinterJobs {
    const sizeStr = width + '*' + height
    const fontSize = FONT_SIZE_MAP[sizeStr] || 'SIZE_NORMAL'
    // @ts-ignore
    this._enqueue(commands.TEXT_FORMAT[fontSize])
    return this
  }

  public setBold(bold: boolean): PrinterJobs {
    if (typeof bold !== 'boolean') {
      bold = true
    }
    this._enqueue(bold ? commands.TEXT_FORMAT.TXT_BOLD_ON : commands.TEXT_FORMAT.TXT_BOLD_OFF)
    return this
  }

  public setUnderline(underline: boolean): PrinterJobs {
    if (typeof underline !== 'boolean') {
      underline = true
    }
    this._enqueue(
      underline ? commands.TEXT_FORMAT.TXT_UNDERL_ON : commands.TEXT_FORMAT.TXT_UNDERL_OFF,
    )
    return this
  }

  public setLineSpacing(height = 3.75): PrinterJobs {
    const n = Math.round(height * 8)
    // this._enqueue(commands.LINE_SPACING.LS_SET);
    this._enqueue([n])
    return this
  }

  public lineFeed(n = 1): PrinterJobs {
    return this.text(new Array(n).fill(commands.EOL).join(''))
  }

  public clear(): PrinterJobs {
    this._queue = Array.from(commands.HARDWARE.HW_INIT)
    return this
  }

  public buffer(): ArrayBuffer {
    return new Uint8Array(this._queue).buffer
  }

  public getQueue(): number[] {
    return this._queue
  }

  //   public setMode(mode: string): PrinterJobs {
  //     this._enqueue(commands.PAGEMODE);
  //     if (mode === "page") {
  //       this._enqueue(commands.MODE.PAGE);
  //     } else if (mode === "standard") {
  //       this._enqueue(commands.MODE.STANDARD);
  //     }
  //     return this;
  //   }

  public setArea(x = 0, y = 0, width = 104, height = 128): PrinterJobs {
    this._enqueue(commands.AREA)
    this._enqueue(transformMmToLH(x))
    this._enqueue(transformMmToLH(y))
    this._enqueue(transformMmToLH(width))
    this._enqueue(transformMmToLH(height))
    return this
  }

  public setPosition(left = 0, top = 0): PrinterJobs {
    this._enqueue(commands.POSITION.LEFT)
    this._enqueue(transformMmToLH(left))
    this._enqueue(commands.POSITION.TOP)
    this._enqueue(transformMmToLH(top))
    return this
  }

  public addQRCode(params: QRCodeParams): PrinterJobs {
    const { text, mode = 50, size = 6, level = 49 } = params
    if (text) {
      this._enqueue([29, 40, 107, 4, 0, 49, 65, mode, 0])
      this._enqueue([29, 40, 107, 3, 0, 49, 67, size])
      this._enqueue([29, 40, 107, 3, 0, 49, 69, level])
      this._enqueue([29, 40, 107])
      this._enqueue(getLHArr(text.length + 3))
      this._enqueue([49, 80, 48])
      this.text(text)
      this._enqueue([29, 40, 107, 3, 0, 49, 81, 48])
    }
    return this
  }

  public addImage(params: { width: number; height: number }): PrinterJobs {
    const { width, height } = params
    const xl = width % 256
    const xh = Math.floor((width - xl) / 256)
    this._enqueue([29, 118, 48, 0, xl, xh, 1, 0])
    return this
  }

  public addBarcode(params: BarcodeParams): PrinterJobs {
    const { text, width = 2, height = 15, textPosition = 0, type = 'CODE39' } = params
    if (text) {
      const ht = Math.round(height * 8)
      this._enqueue(commands.BARCODE_FORMAT.BARCODE_HEIGHT(ht))
      this._enqueue(commands.BARCODE_FORMAT.BARCODE_WIDTH(width))
      const textPosKey = BARCODE_TXT_MAP[textPosition]
      // @ts-ignore
      this._enqueue(commands.BARCODE_FORMAT[textPosKey])
      // @ts-ignore
      this._enqueue(commands.BARCODE_FORMAT['BARCODE_' + type])
      if (type === 'CODE128') {
        const len = text.length
        const nL = len % 256
        this._enqueue([nL])
        this.text(text)
      } else {
        this.text(text)
        this._enqueue([0])
      }
    }
    return this
  }

  public println(content?: string): PrinterJobs {
    if (content) {
      this.text(content)
      this._enqueue(commands.LF)
    } else {
      this._enqueue(commands.ESC)
      this._enqueue([0])
    }
    return this
  }

  public print(): PrinterJobs {
    this._enqueue(commands.FF)
    return this
  }

  public printPage(): PrinterJobs {
    this._enqueue(commands.ESC_FF)
    return this
  }
}

export default PrinterJobs
